

package com.hazelcast.internal.dynamicconfig;

import com.hazelcast.config.*;
import com.hazelcast.config.vector.VectorCollectionConfig;
import com.hazelcast.config.vector.VectorIndexConfig;
import com.hazelcast.instance.ProtocolType;
import com.hazelcast.internal.config.AliasedDiscoveryConfigUtils;
import com.hazelcast.internal.namespace.ResourceDefinition;
import com.hazelcast.internal.util.CollectionUtil;
import com.hazelcast.memory.Capacity;
import org.snakeyaml.engine.v2.api.Dump;
import org.snakeyaml.engine.v2.api.DumpSettings;
import org.snakeyaml.engine.v2.common.FlowStyle;

import java.util.*;
import java.util.stream.Collectors;

import static com.hazelcast.config.ConfigAccessor.getActiveMemberNetworkConfig;
import static com.hazelcast.config.ConfigXmlGenerator.MASK_FOR_SENSITIVE_DATA;
import static com.hazelcast.config.ConfigXmlGenerator.endpointConfigElementName;
import static com.hazelcast.internal.config.AliasedDiscoveryConfigUtils.aliasedDiscoveryConfigsFrom;
import static com.hazelcast.internal.config.ConfigSections.LICENSE_KEY;
import static com.hazelcast.internal.dynamicconfig.DynamicConfigXmlGenerator.classNameOrImplClass;
import static com.hazelcast.internal.util.StringUtil.isNullOrEmpty;
import static java.lang.Boolean.TRUE;

/**
 * YAML counterpart of {@link ConfigXmlGenerator}. Note that this class isn't
 * complete like its counterpart. However, since dynamic configuration
 * persistence depends on this class, dynamically configurable configurations
 * must be added to this class.
 */
@SuppressWarnings({"checkstyle:MethodCount", "checkstyle:ClassFanOutComplexity"})
public class DynamicConfigYamlGenerator {

    private static final int INDENT = 2;
    private static final boolean DEFAULT_MASK_SENSITIVE_FIELDS = false;
    private static volatile boolean maskSensitiveFields = DEFAULT_MASK_SENSITIVE_FIELDS;

    public static void licenseKeyYamlGenerator(Map<String, Object> parent, Config config) {
        addNonNullToMap(parent, LICENSE_KEY.getName(), getOrMaskValue(config.getLicenseKey()));
    }

    @SuppressWarnings("checkstyle:MethodLength")
    public static void mapYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getMapConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (MapConfig subConfigAsObject : config.getMapConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            String cacheDeserializedValues = subConfigAsObject.getCacheDeserializedValues() != null ? subConfigAsObject.getCacheDeserializedValues().name().replace('_', '-') : null;

            addNonNullToMap(subConfigAsMap, "in-memory-format", subConfigAsObject.getInMemoryFormat().name());
            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());
            addNonNullToMap(subConfigAsMap, "per-entry-stats-enabled", subConfigAsObject.isPerEntryStatsEnabled());
            addNonNullToMap(subConfigAsMap, "cache-deserialized-values", cacheDeserializedValues);
            addNonNullToMap(subConfigAsMap, "backup-count", subConfigAsObject.getBackupCount());
            addNonNullToMap(subConfigAsMap, "async-backup-count", subConfigAsObject.getAsyncBackupCount());
            addNonNullToMap(subConfigAsMap, "time-to-live-seconds", subConfigAsObject.getTimeToLiveSeconds());
            addNonNullToMap(subConfigAsMap, "max-idle-seconds", subConfigAsObject.getMaxIdleSeconds());
            addNonNullToMap(subConfigAsMap, "merge-policy", getMergePolicyConfigAsMap(subConfigAsObject.getMergePolicyConfig()));
            addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", subConfigAsObject.getSplitBrainProtectionName());
            addNonNullToMap(subConfigAsMap, "read-backup-data", subConfigAsObject.isReadBackupData());
            addNonNullToMap(subConfigAsMap, "metadata-policy", subConfigAsObject.getMetadataPolicy().name());
            addNonNullToMap(subConfigAsMap, "eviction", getEvictionConfigAsMap(subConfigAsObject.getEvictionConfig()));
            addNonNullToMap(subConfigAsMap, "merkle-tree", getMerkleTreeConfigAsMap(subConfigAsObject.getMerkleTreeConfig()));
            addNonNullToMap(subConfigAsMap, "event-journal", getEventJournalConfigAsMap(subConfigAsObject.getEventJournalConfig()));
            addNonNullToMap(subConfigAsMap, "data-persistence", getDataPersistenceConfigAsMap(subConfigAsObject.getDataPersistenceConfig()));
            addNonNullToMap(subConfigAsMap, "map-store", getMapStoreConfigAsMap(subConfigAsObject.getMapStoreConfig()));
            addNonNullToMap(subConfigAsMap, "near-cache", getNearCacheConfigAsMap(subConfigAsObject.getNearCacheConfig()));
            addNonNullToMap(subConfigAsMap, "wan-replication-ref", getWanReplicationRefAsMap(subConfigAsObject.getWanReplicationRef(), true));
            addNonNullToMap(subConfigAsMap, "near-cache", getNearCacheConfigAsMap(subConfigAsObject.getNearCacheConfig()));
            addNonNullToMap(subConfigAsMap, "indexes", getIndexConfigsAsList(subConfigAsObject.getIndexConfigs()));
            addNonNullToMap(subConfigAsMap, "attributes", getAttributeConfigsAsMap(subConfigAsObject.getAttributeConfigs()));
            addNonNullToMap(subConfigAsMap, "entry-listeners", getEntryListenerConfigsAsList(subConfigAsObject.getEntryListenerConfigs()));
            addNonNullToMap(subConfigAsMap, "partition-lost-listeners", getListenerConfigsAsList(subConfigAsObject.getPartitionLostListenerConfigs()));
            addNonNullToMap(subConfigAsMap, "partition-strategy", getPartitioningStrategyAsString(subConfigAsObject.getPartitioningStrategyConfig()));
            addNonNullToMap(subConfigAsMap, "query-caches", getQueryCacheConfigsAsMap(subConfigAsObject.getQueryCacheConfigs()));
            addNonNullToMap(subConfigAsMap, "tiered-store", getTieredStoreConfigAsMap(subConfigAsObject.getTieredStoreConfig()));
            addNonNullToMap(subConfigAsMap, "partition-attributes", getPartitioningAttributesAsList(subConfigAsObject.getPartitioningAttributeConfigs()));
            addNonNullToMap(subConfigAsMap, "user-code-namespace", subConfigAsObject.getUserCodeNamespace());

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("map", child);
    }

    public static void cacheYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getCacheConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (CacheSimpleConfig subConfigAsObject : config.getCacheConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "key-type", wrapObjectWithMap("class-name", subConfigAsObject.getKeyType()));
            addNonNullToMap(subConfigAsMap, "value-type", wrapObjectWithMap("class-name", subConfigAsObject.getValueType()));
            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());
            addNonNullToMap(subConfigAsMap, "management-enabled", subConfigAsObject.isManagementEnabled());
            addNonNullToMap(subConfigAsMap, "read-through", subConfigAsObject.isReadThrough());
            addNonNullToMap(subConfigAsMap, "write-through", subConfigAsObject.isWriteThrough());
            addNonNullToMap(subConfigAsMap, "cache-loader-factory", wrapObjectWithMap("class-name", subConfigAsObject.getCacheLoaderFactory()));
            addNonNullToMap(subConfigAsMap, "cache-writer-factory", wrapObjectWithMap("class-name", subConfigAsObject.getCacheWriterFactory()));
            addNonNullToMap(subConfigAsMap, "cache-loader", wrapObjectWithMap("class-name", subConfigAsObject.getCacheLoader()));
            addNonNullToMap(subConfigAsMap, "cache-writer", wrapObjectWithMap("class-name", subConfigAsObject.getCacheWriter()));
            addNonNullToMap(subConfigAsMap, "expiry-policy-factory", getExpiryPolicyFactoryConfigAsMap(subConfigAsObject.getExpiryPolicyFactoryConfig()));
            addNonNullToMap(subConfigAsMap, "cache-entry-listeners", getCacheSimpleEntryListenerConfigsAsList(subConfigAsObject.getCacheEntryListeners()));
            addNonNullToMap(subConfigAsMap, "in-memory-format", subConfigAsObject.getInMemoryFormat().name());
            addNonNullToMap(subConfigAsMap, "backup-count", subConfigAsObject.getBackupCount());
            addNonNullToMap(subConfigAsMap, "async-backup-count", subConfigAsObject.getAsyncBackupCount());
            addNonNullToMap(subConfigAsMap, "eviction", getEvictionConfigAsMap(subConfigAsObject.getEvictionConfig()));
            addNonNullToMap(subConfigAsMap, "wan-replication-ref", getWanReplicationRefAsMap(subConfigAsObject.getWanReplicationRef(), false));
            addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", subConfigAsObject.getSplitBrainProtectionName());
            addNonNullToMap(subConfigAsMap, "partition-lost-listeners", getListenerConfigsAsList(subConfigAsObject.getPartitionLostListenerConfigs()));
            addNonNullToMap(subConfigAsMap, "merge-policy", getMergePolicyConfigAsMap(subConfigAsObject.getMergePolicyConfig()));
            addNonNullToMap(subConfigAsMap, "event-journal", getEventJournalConfigAsMap(subConfigAsObject.getEventJournalConfig()));
            addNonNullToMap(subConfigAsMap, "data-persistence", getDataPersistenceConfigAsMap(subConfigAsObject.getDataPersistenceConfig()));
            addNonNullToMap(subConfigAsMap, "merkle-tree", getMerkleTreeConfigAsMap(subConfigAsObject.getMerkleTreeConfig()));
            addNonNullToMap(subConfigAsMap, "disable-per-entry-invalidation-events", subConfigAsObject.isDisablePerEntryInvalidationEvents());
            addNonNullToMap(subConfigAsMap, "user-code-namespace", subConfigAsObject.getUserCodeNamespace());

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("cache", child);
    }

    public static void queueYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getQueueConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (QueueConfig subConfigAsObject : config.getQueueConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "priority-comparator-class-name", subConfigAsObject.getPriorityComparatorClassName());
            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());
            addNonNullToMap(subConfigAsMap, "max-size", subConfigAsObject.getMaxSize());
            addNonNullToMap(subConfigAsMap, "backup-count", subConfigAsObject.getBackupCount());
            addNonNullToMap(subConfigAsMap, "async-backup-count", subConfigAsObject.getAsyncBackupCount());
            addNonNullToMap(subConfigAsMap, "empty-queue-ttl", subConfigAsObject.getEmptyQueueTtl());
            addNonNullToMap(subConfigAsMap, "item-listeners", getItemListenerConfigsAsList(subConfigAsObject.getItemListenerConfigs()));
            addNonNullToMap(subConfigAsMap, "queue-store", getQueueStoreConfigAsMap(subConfigAsObject.getQueueStoreConfig()));
            addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", subConfigAsObject.getSplitBrainProtectionName());
            addNonNullToMap(subConfigAsMap, "user-code-namespace", subConfigAsObject.getUserCodeNamespace());
            addNonNullToMap(subConfigAsMap, "merge-policy", getMergePolicyConfigAsMap(subConfigAsObject.getMergePolicyConfig()));

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("queue", child);
    }

    public static void listYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getListConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (ListConfig subConfigAsObject : config.getListConfigs().values()) {
            child.put(subConfigAsObject.getName(), getCollectionConfigAsMap(subConfigAsObject));
        }

        parent.put("list", child);
    }

    public static void setYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getSetConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (SetConfig subConfigAsObject : config.getSetConfigs().values()) {
            child.put(subConfigAsObject.getName(), getCollectionConfigAsMap(subConfigAsObject));
        }

        parent.put("set", child);
    }

    public static void multiMapYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getMultiMapConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (MultiMapConfig subConfigAsObject : config.getMultiMapConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "backup-count", subConfigAsObject.getBackupCount());
            addNonNullToMap(subConfigAsMap, "async-backup-count", subConfigAsObject.getAsyncBackupCount());
            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());
            addNonNullToMap(subConfigAsMap, "binary", subConfigAsObject.isBinary());
            addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", subConfigAsObject.getSplitBrainProtectionName());
            addNonNullToMap(subConfigAsMap, "value-collection-type", subConfigAsObject.getValueCollectionType().name());
            addNonNullToMap(subConfigAsMap, "entry-listeners", getEntryListenerConfigsAsList(subConfigAsObject.getEntryListenerConfigs()));
            addNonNullToMap(subConfigAsMap, "merge-policy", getMergePolicyConfigAsMap(subConfigAsObject.getMergePolicyConfig()));
            addNonNullToMap(subConfigAsMap, "user-code-namespace", subConfigAsObject.getUserCodeNamespace());

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("multimap", child);
    }

    public static void replicatedMapYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getReplicatedMapConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (ReplicatedMapConfig subConfigAsObject : config.getReplicatedMapConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "in-memory-format", subConfigAsObject.getInMemoryFormat().name());
            addNonNullToMap(subConfigAsMap, "async-fillup", subConfigAsObject.isAsyncFillup());
            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());
            addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", subConfigAsObject.getSplitBrainProtectionName());
            addNonNullToMap(subConfigAsMap, "merge-policy", getMergePolicyConfigAsMap(subConfigAsObject.getMergePolicyConfig()));
            addNonNullToMap(subConfigAsMap, "entry-listeners", getEntryListenerConfigsAsList(subConfigAsObject.getListenerConfigs()));
            addNonNullToMap(subConfigAsMap, "user-code-namespace", subConfigAsObject.getUserCodeNamespace());

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("replicatedmap", child);
    }

    public static void ringbufferYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getRingbufferConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (RingbufferConfig subConfigAsObject : config.getRingbufferConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "capacity", subConfigAsObject.getCapacity());
            addNonNullToMap(subConfigAsMap, "time-to-live-seconds", subConfigAsObject.getTimeToLiveSeconds());
            addNonNullToMap(subConfigAsMap, "backup-count", subConfigAsObject.getBackupCount());
            addNonNullToMap(subConfigAsMap, "async-backup-count", subConfigAsObject.getAsyncBackupCount());
            addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", subConfigAsObject.getSplitBrainProtectionName());
            addNonNullToMap(subConfigAsMap, "in-memory-format", subConfigAsObject.getInMemoryFormat().name());
            addNonNullToMap(subConfigAsMap, "ringbuffer-store", getRingbufferStoreConfigAsMap(subConfigAsObject.getRingbufferStoreConfig()));
            addNonNullToMap(subConfigAsMap, "merge-policy", getMergePolicyConfigAsMap(subConfigAsObject.getMergePolicyConfig()));
            addNonNullToMap(subConfigAsMap, "user-code-namespace", subConfigAsObject.getUserCodeNamespace());

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("ringbuffer", child);
    }

    public static void topicYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getTopicConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (TopicConfig subConfigAsObject : config.getTopicConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());
            addNonNullToMap(subConfigAsMap, "global-ordering-enabled", subConfigAsObject.isGlobalOrderingEnabled());
            addNonNullToMap(subConfigAsMap, "message-listeners", getListenerConfigsAsList(subConfigAsObject.getMessageListenerConfigs()));
            addNonNullToMap(subConfigAsMap, "multi-threading-enabled", subConfigAsObject.isMultiThreadingEnabled());
            addNonNullToMap(subConfigAsMap, "user-code-namespace", subConfigAsObject.getUserCodeNamespace());

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("topic", child);
    }

    public static void reliableTopicYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getReliableTopicConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (ReliableTopicConfig subConfigAsObject : config.getReliableTopicConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());
            addNonNullToMap(subConfigAsMap, "read-batch-size", subConfigAsObject.getReadBatchSize());
            addNonNullToMap(subConfigAsMap, "topic-overload-policy", subConfigAsObject.getTopicOverloadPolicy().name());
            addNonNullToMap(subConfigAsMap, "message-listeners", getListenerConfigsAsList(subConfigAsObject.getMessageListenerConfigs()));
            addNonNullToMap(subConfigAsMap, "user-code-namespace", subConfigAsObject.getUserCodeNamespace());
            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("reliable-topic", child);
    }

    public static void executorYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getExecutorConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (ExecutorConfig subConfigAsObject : config.getExecutorConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());
            addNonNullToMap(subConfigAsMap, "pool-size", subConfigAsObject.getPoolSize());
            addNonNullToMap(subConfigAsMap, "queue-capacity", subConfigAsObject.getQueueCapacity());
            addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", subConfigAsObject.getSplitBrainProtectionName());
            addNonNullToMap(subConfigAsMap, "user-code-namespace", subConfigAsObject.getUserCodeNamespace());

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("executor-service", child);
    }

    public static void durableExecutorYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getDurableExecutorConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (DurableExecutorConfig subConfigAsObject : config.getDurableExecutorConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "pool-size", subConfigAsObject.getPoolSize());
            addNonNullToMap(subConfigAsMap, "durability", subConfigAsObject.getDurability());
            addNonNullToMap(subConfigAsMap, "capacity", subConfigAsObject.getCapacity());
            addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", subConfigAsObject.getSplitBrainProtectionName());
            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());
            addNonNullToMap(subConfigAsMap, "user-code-namespace", subConfigAsObject.getUserCodeNamespace());

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("durable-executor-service", child);
    }

    public static void scheduledExecutorYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getScheduledExecutorConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (ScheduledExecutorConfig subConfigAsObject : config.getScheduledExecutorConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "pool-size", subConfigAsObject.getPoolSize());
            addNonNullToMap(subConfigAsMap, "durability", subConfigAsObject.getDurability());
            addNonNullToMap(subConfigAsMap, "capacity", subConfigAsObject.getCapacity());
            addNonNullToMap(subConfigAsMap, "capacity-policy", subConfigAsObject.getCapacityPolicy().name());
            addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", subConfigAsObject.getSplitBrainProtectionName());
            addNonNullToMap(subConfigAsMap, "merge-policy", getMergePolicyConfigAsMap(subConfigAsObject.getMergePolicyConfig()));
            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());
            addNonNullToMap(subConfigAsMap, "user-code-namespace", subConfigAsObject.getUserCodeNamespace());
            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("scheduled-executor-service", child);
    }

    public static void cardinalityEstimatorYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getCardinalityEstimatorConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (CardinalityEstimatorConfig subConfigAsObject : config.getCardinalityEstimatorConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "backup-count", subConfigAsObject.getBackupCount());
            addNonNullToMap(subConfigAsMap, "async-backup-count", subConfigAsObject.getAsyncBackupCount());
            addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", subConfigAsObject.getSplitBrainProtectionName());
            addNonNullToMap(subConfigAsMap, "merge-policy", getMergePolicyConfigAsMap(subConfigAsObject.getMergePolicyConfig()));

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("cardinality-estimator", child);
    }

    public static void flakeIdGeneratorYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getFlakeIdGeneratorConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (FlakeIdGeneratorConfig subConfigAsObject : config.getFlakeIdGeneratorConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "prefetch-count", subConfigAsObject.getPrefetchCount());
            addNonNullToMap(subConfigAsMap, "prefetch-validity-millis", subConfigAsObject.getPrefetchValidityMillis());
            addNonNullToMap(subConfigAsMap, "epoch-start", subConfigAsObject.getEpochStart());
            addNonNullToMap(subConfigAsMap, "node-id-offset", subConfigAsObject.getNodeIdOffset());
            addNonNullToMap(subConfigAsMap, "bits-sequence", subConfigAsObject.getBitsSequence());
            addNonNullToMap(subConfigAsMap, "bits-node-id", subConfigAsObject.getBitsNodeId());
            addNonNullToMap(subConfigAsMap, "allowed-future-millis", subConfigAsObject.getAllowedFutureMillis());
            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("flake-id-generator", child);
    }

    public static void pnCounterYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getPNCounterConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (PNCounterConfig subConfigAsObject : config.getPNCounterConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "replica-count", subConfigAsObject.getReplicaCount());
            addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", subConfigAsObject.getSplitBrainProtectionName());
            addNonNullToMap(subConfigAsMap, "statistics-enabled", subConfigAsObject.isStatisticsEnabled());

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("pn-counter", child);
    }

    public static void dataConnectionYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getDataConnectionConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (DataConnectionConfig dataConnectionConfig : config.getDataConnectionConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "type", dataConnectionConfig.getType());
            addNonNullToMap(subConfigAsMap, "shared", dataConnectionConfig.isShared());
            addNonNullToMap(subConfigAsMap, "properties", getPropertiesAsMap(dataConnectionConfig.getProperties()));

            child.put(dataConnectionConfig.getName(), subConfigAsMap);
        }

        parent.put("data-connection", child);
    }

    public static void wanReplicationYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getWanReplicationConfigs().isEmpty()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        for (WanReplicationConfig subConfigAsObject : config.getWanReplicationConfigs().values()) {
            Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(subConfigAsMap, "batch-publisher", getWanBatchPublisherConfigsAsMap(subConfigAsObject.getBatchPublisherConfigs()));
            addNonNullToMap(subConfigAsMap, "custom-publisher", getWanCustomPublisherConfigsAsMap(subConfigAsObject.getCustomPublisherConfigs()));
            addNonNullToMap(subConfigAsMap, "consumer", getWanConsumerConfigsAsMap(subConfigAsObject.getConsumerConfig()));

            child.put(subConfigAsObject.getName(), subConfigAsMap);
        }

        parent.put("wan-replication", child);
    }

    public static void tcpIpConfigYamlGenerator(Map<String, Object> parent, Config config) {
        TcpIpConfig tcpIpConfig = getActiveMemberNetworkConfig(config).getJoin().getTcpIpConfig();
        Map<String, Object> child = new LinkedHashMap<>();
        child.put("enabled", tcpIpConfig.isEnabled());
        child.put("connection-timeout-seconds", tcpIpConfig.getConnectionTimeoutSeconds());
        addNonNullToMap(child, "member-list", tcpIpConfig.getMembers());
        addNonNullToMap(child, "required-member", tcpIpConfig.getRequiredMember());
        parent.put("tcp-ip", child);
    }

    private static void autoDetectionConfigYamlGenerator(Map<String, Object> parent, JoinConfig joinConfig) {
        Map<String, Object> child = new LinkedHashMap<>();
        child.put("enabled", joinConfig.getAutoDetectionConfig().isEnabled());
        parent.put("auto-detection", child);
    }

    private static void multicastConfigYamlGenerator(Map<String, Object> parent, JoinConfig joinConfig) {
        Map<String, Object> child = new LinkedHashMap<>();
        MulticastConfig multicastCfg = joinConfig.getMulticastConfig();
        if (multicastCfg == null) {
            return;
        }
        addNonNullToMap(child, "enabled", multicastCfg.isEnabled());
        addNonNullToMap(child, "loopback-mode-enabled", multicastCfg.getLoopbackModeEnabled());
        addNonNullToMap(child, "multicast-group", multicastCfg.getMulticastGroup());
        addNonNullToMap(child, "multicast-port", multicastCfg.getMulticastPort());
        addNonNullToMap(child, "multicast-timeout-seconds", multicastCfg.getMulticastTimeoutSeconds());
        addNonNullToMap(child, "multicast-time-to-live", multicastCfg.getMulticastTimeToLive());
        addNonNullToMap(child, "trusted-interfaces", new ArrayList<>(multicastCfg.getTrustedInterfaces()));
        parent.put("multicast", child);
    }

    private static void aliasedDiscoveryConfigsYamlGenerator(Map<String, Object> parent, List<AliasedDiscoveryConfig<?>> configs) {
        if (configs == null) {
            return;
        }
        for (AliasedDiscoveryConfig<?> c : configs) {
            Map<String, Object> child = new LinkedHashMap<>();
            child.put("enabled", c.isEnabled());
            if (c.isUsePublicIp()) {
                child.put("use-public-ip", "true");
            }
            if (!c.getProperties().isEmpty()) {
                child.put("properties", c.getProperties());
            }

            parent.put(AliasedDiscoveryConfigUtils.tagFor(c), child);
        }
    }

    private static void discoveryStrategyConfigYamlGenerator(Map<String, Object> parent, DiscoveryConfig discoveryConfig) {
        if (discoveryConfig == null) {
            return;
        }
        Map<String, Object> child = new LinkedHashMap<>();
        String nodeFilterClass = classNameOrImplClass(discoveryConfig.getNodeFilterClass(), discoveryConfig.getNodeFilter());
        if (nodeFilterClass != null) {
            child.put("node-filter", wrapObjectWithMap("class", nodeFilterClass));
        }

        Collection<DiscoveryStrategyConfig> configs = discoveryConfig.getDiscoveryStrategyConfigs();
        if (CollectionUtil.isNotEmpty(configs)) {
            for (DiscoveryStrategyConfig config : configs) {
                Map<String, Object> discoveryStrategySubConfig = new LinkedHashMap<>();
                addNonNullToMap(discoveryStrategySubConfig, "class", classNameOrImplClass(config.getClassName(), config.getDiscoveryStrategyFactory()));
                addNonNullToMap(discoveryStrategySubConfig, "enabled", classNameOrImplClass(config.getClassName(), config.getDiscoveryStrategyFactory()));
                addNonNullToMap(discoveryStrategySubConfig, "properties", config.getProperties());
                child.put("discovery-strategy", discoveryStrategySubConfig);
            }
        }
        parent.put("discovery-strategies", child);
    }

    private static void sslConfigYamlGenerator(Map<String, Object> parent, SSLConfig sslConfig) {
        if (sslConfig == null) {
            return;
        }
        Map<String, Object> child = new LinkedHashMap<>();
        sslConfig = new SSLConfig(sslConfig);
        child.put("enabled", sslConfig.isEnabled());

        String factoryClassName = classNameOrImplClass(sslConfig.getFactoryClassName(), sslConfig.getFactoryImplementation());
        if (factoryClassName != null) {
            sslConfig.setFactoryClassName(factoryClassName);
            child.put("factory-class-name", factoryClassName);
        }

        Properties props = sslConfig.getProperties();

        if (maskSensitiveFields && props.containsKey("trustStorePassword")) {
            props.setProperty("trustStorePassword", MASK_FOR_SENSITIVE_DATA);
        }

        if (maskSensitiveFields && props.containsKey("keyStorePassword")) {
            props.setProperty("keyStorePassword", MASK_FOR_SENSITIVE_DATA);
        }
        addNonNullToMap(child, "properties", props);

        parent.put("ssl", child);
    }

    private static void interfacesConfigYamlGenerator(Map<String, Object> parent, InterfacesConfig interfaces) {
        if (interfaces.getInterfaces() == null || interfaces.getInterfaces().isEmpty()) {
            return;
        }
        Map<String, Object> child = new LinkedHashMap<>();
        child.put("enabled", interfaces.isEnabled());
        child.put("interfaces", new ArrayList<>(interfaces.getInterfaces()));
        parent.put("interfaces", child);
    }

    private static void socketInterceptorConfigYamlGenerator(Map<String, Object> parent, SocketInterceptorConfig socketInterceptorCfg) {
        if (socketInterceptorCfg == null) {
            return;
        }
        Map<String, Object> child = new LinkedHashMap<>();
        child.put("enabled", socketInterceptorCfg.isEnabled());
        String className = classNameOrImplClass(socketInterceptorCfg.getClassName(), socketInterceptorCfg.getImplementation());
        addNonNullToMap(child, "class-name", className);
        addNonNullToMap(child, "properties", getPropertiesAsMap(socketInterceptorCfg.getProperties()));
        parent.put("socket-interceptor", child);
    }

    private static void symmetricEncInterceptorConfigYamlGenerator(Map<String, Object> parent, SymmetricEncryptionConfig sec) {
        if (sec == null) {
            return;
        }
        Map<String, Object> child = new LinkedHashMap<>();
        child.put("enabled", sec.isEnabled());
        addNonNullToMap(child, "algorithm", sec.getAlgorithm());
        addNonNullToMap(child, "salt", getOrMaskValue(sec.getSalt()));
        addNonNullToMap(child, "password", getOrMaskValue(sec.getPassword()));
        addNonNullToMap(child, "iteration-count", sec.getIterationCount());
        parent.put("symmetric-encryption", child);
    }

    private static void memberAddressProviderConfigYamlGenerator(Map<String, Object> parent, MemberAddressProviderConfig memberAddressProviderConfig) {
        if (memberAddressProviderConfig == null) {
            return;
        }
        String className = classNameOrImplClass(memberAddressProviderConfig.getClassName(), memberAddressProviderConfig.getImplementation());
        if (isNullOrEmpty(className)) {
            return;
        }
        Map<String, Object> child = new LinkedHashMap<>();
        child.put("enabled", memberAddressProviderConfig.isEnabled());
        child.put("class-name", className);
        addNonNullToMap(child, "properties", getPropertiesAsMap(memberAddressProviderConfig.getProperties()));
        parent.put("member-address-provider", child);
    }

    private static void failureDetectorConfigYamlGenerator(Map<String, Object> parent, IcmpFailureDetectorConfig icmpFailureDetectorConfig) {
        if (icmpFailureDetectorConfig == null) {
            return;
        }
        Map<String, Object> child = new LinkedHashMap<>();
        addNonNullToMap(child, "enabled", icmpFailureDetectorConfig.isEnabled());
        addNonNullToMap(child, "timeout-milliseconds", icmpFailureDetectorConfig.getTimeoutMilliseconds());
        addNonNullToMap(child, "fail-fast-on-startup", icmpFailureDetectorConfig.isFailFastOnStartup());
        addNonNullToMap(child, "interval-milliseconds", icmpFailureDetectorConfig.getIntervalMilliseconds());
        addNonNullToMap(child, "max-attempts", icmpFailureDetectorConfig.getMaxAttempts());
        addNonNullToMap(child, "parallel-mode", icmpFailureDetectorConfig.isParallelMode());
        addNonNullToMap(child, "ttl", icmpFailureDetectorConfig.getTtl());

        parent.put("failure-detector", wrapObjectWithMap("icmp", child));
    }

    private static void restApiYamlGenerator(Map<String, Object> parent, RestApiConfig restApiConfig) {
        if (restApiConfig == null) {
            return;
        }
        Map<String, Object> child = new LinkedHashMap<>();
        child.put("enabled", restApiConfig.isEnabled());

        Map<String, Object> endpointGroupsAsMap = new LinkedHashMap<>();
        Set<RestEndpointGroup> enabledGroups = restApiConfig.getEnabledGroups();
        for (RestEndpointGroup endpointGroup : RestEndpointGroup.getAllEndpointGroups()) {
            endpointGroupsAsMap.put(endpointGroup.name(), wrapObjectWithMap("enabled", enabledGroups.stream().anyMatch(enabledGroup -> enabledGroup.getCode() == endpointGroup.getCode())));
        }
        child.put("endpoint-groups", endpointGroupsAsMap);
        parent.put("rest-api", child);
    }

    private static void memcacheProtocolYamlGenerator(Map<String, Object> parent, MemcacheProtocolConfig memcacheProtocolConfig) {
        if (memcacheProtocolConfig == null) {
            return;
        }
        parent.put("memcache-protocol", wrapObjectWithMap("enabled", memcacheProtocolConfig.isEnabled()));
    }

    private static void endpointConfigYamlGenerator(Map<String, Object> parent, EndpointConfig endpointConfig) {
        Map<String, Object> child = new LinkedHashMap<>();

        if (endpointConfig.getName() != null && endpointConfig.getProtocolType() != ProtocolType.WAN) {
            child.put("name", endpointConfig.getName());
        }

        Collection<String> outboundPortDefinitions = endpointConfig.getOutboundPortDefinitions();
        if (CollectionUtil.isNotEmpty(outboundPortDefinitions)) {
            addNonNullToMap(child, "outbound-ports", new ArrayList<>(outboundPortDefinitions));
        }
        interfacesConfigYamlGenerator(child, endpointConfig.getInterfaces());
        sslConfigYamlGenerator(child, endpointConfig.getSSLConfig());
        socketInterceptorConfigYamlGenerator(child, endpointConfig.getSocketInterceptorConfig());
        symmetricEncInterceptorConfigYamlGenerator(child, endpointConfig.getSymmetricEncryptionConfig());

        if (endpointConfig instanceof RestServerEndpointConfig restServerEndpointConfig) {
            Map<String, Object> endpointGroupsAsMap = new LinkedHashMap<>();
            Set<RestEndpointGroup> enabledGroups = restServerEndpointConfig.getEnabledGroups();
            for (RestEndpointGroup endpointGroup : RestEndpointGroup.getAllEndpointGroups()) {
                endpointGroupsAsMap.put(endpointGroup.name(), wrapObjectWithMap("enabled", enabledGroups.stream().anyMatch(enabledGroup -> enabledGroup.getCode() == endpointGroup.getCode())));
            }
            child.put("endpoint-groups", endpointGroupsAsMap);
        }

        // socket-options
        Map<String, Object> socketOptions = new LinkedHashMap<>();
        addNonNullToMap(socketOptions, "buffer-direct", endpointConfig.isSocketBufferDirect());
        addNonNullToMap(socketOptions, "tcp-no-delay", endpointConfig.isSocketTcpNoDelay());
        addNonNullToMap(socketOptions, "keep-alive", endpointConfig.isSocketKeepAlive());
        addNonNullToMap(socketOptions, "connect-timeout-seconds", endpointConfig.getSocketConnectTimeoutSeconds());
        addNonNullToMap(socketOptions, "send-buffer-size-kb", endpointConfig.getSocketSendBufferSizeKb());
        addNonNullToMap(socketOptions, "receive-buffer-size-kb", endpointConfig.getSocketRcvBufferSizeKb());
        addNonNullToMap(socketOptions, "linger-seconds", endpointConfig.getSocketLingerSeconds());
        if (!socketOptions.isEmpty()) {
            addNonNullToMap(child, "socket-options", socketOptions);
        }

        if (endpointConfig instanceof ServerSocketEndpointConfig serverSocketEndpointConfig) {

            Map<String, Object> portCfg = new LinkedHashMap<>();
            addNonNullToMap(portCfg, "port", serverSocketEndpointConfig.getPort());
            addNonNullToMap(portCfg, "port-count", serverSocketEndpointConfig.getPortCount());
            addNonNullToMap(portCfg, "auto-increment", serverSocketEndpointConfig.isPortAutoIncrement());
            addNonNullToMap(child, "public-address", serverSocketEndpointConfig.getPublicAddress());
            addNonNullToMap(child, "reuse-address", serverSocketEndpointConfig.isReuseAddress());
            addNonNullToMap(child, "port", portCfg);
        }
        if (endpointConfig.getName() != null && endpointConfig.getProtocolType() == ProtocolType.WAN) {
            parent.put(endpointConfigElementName(endpointConfig), wrapObjectWithMap(endpointConfig.getName(), child));
        } else {
            parent.put(endpointConfigElementName(endpointConfig), child);
        }
    }

    static Map<String, Object> javaSerializationFilterGenerator(JavaSerializationFilterConfig jsfConfig) {
        Map<String, Object> javaSerializationFilterCfg = new LinkedHashMap<>();
        addNonNullToMap(javaSerializationFilterCfg, "defaults-disabled", jsfConfig.isDefaultsDisabled());
        Map<String, Object> whiteListAsMap = classFilterGenerator(jsfConfig.getWhitelist());
        Map<String, Object> blackListAsMap = classFilterGenerator(jsfConfig.getBlacklist());
        javaSerializationFilterCfg.put("blacklist", blackListAsMap);
        javaSerializationFilterCfg.put("whitelist", whiteListAsMap);
        return javaSerializationFilterCfg;
    }

    static Map<String, Object> classFilterGenerator(ClassFilter classFilter) {
        if (classFilter == null || classFilter.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, Object> classFilterMap = new LinkedHashMap<>();
        List<String> packagesAsList = new ArrayList<>();
        List<String> classesAsList = new ArrayList<>();
        List<String> prefixesAsList = new ArrayList<>();
        for (String p : classFilter.getPackages()) {
            packagesAsList.add(p);
        }
        for (String c : classFilter.getClasses()) {
            classesAsList.add(c);
        }
        for (String p : classFilter.getPrefixes()) {
            prefixesAsList.add(p);
        }
        classFilterMap.put("package", packagesAsList);
        classFilterMap.put("class", classesAsList);
        classFilterMap.put("prefix", prefixesAsList);
        return classFilterMap;
    }

    public static void namespacesConfigGenerator(Map<String, Object> parent, Config config) {
        Map<String, Object> child = new LinkedHashMap<>();
        UserCodeNamespacesConfig userCodeNamespacesConfig = config.getNamespacesConfig();
        addNonNullToMap(child, "enabled", userCodeNamespacesConfig.isEnabled());
        parent.put("user-code-namespaces", child);

        if (userCodeNamespacesConfig.getClassFilterConfig() != null) {
            Map<String, Object> javaSerializationFilterCfg = javaSerializationFilterGenerator(userCodeNamespacesConfig.getClassFilterConfig());
            addNonNullToMap(child, "class-filter", javaSerializationFilterCfg);
        }

        namespaceConfigGenerator(child, config);
    }

    public static void namespaceConfigGenerator(Map<String, Object> parent, Config config) {
        ConfigAccessor.getNamespaceConfigs(config.getNamespacesConfig()).forEach((namespaceName, namespaceConfig) -> {
            List<Map<String, Object>> items = new ArrayList<>();

            for (ResourceDefinition resourceDefinition : ConfigAccessor.getResourceDefinitions(namespaceConfig)) {
                Map<String, Object> subConfigAsMap = new LinkedHashMap<>();
                subConfigAsMap.put("id", resourceDefinition.id());
                subConfigAsMap.put("resource-type", resourceDefinition.type().toString());
                subConfigAsMap.put("url", resourceDefinition.url());

                items.add(subConfigAsMap);
            }

            parent.put(namespaceName, items);
        });
    }

    private static void metricsConfigGenerator(Map<String, Object> parent, Config config) {
        MetricsConfig metricsConfig = config.getMetricsConfig();
        Map<String, Object> child = new LinkedHashMap<>();

        parent.put("metrics", child);
        child.put("enabled", metricsConfig.isEnabled());
        if (!metricsConfig.isEnabled()) {
            return;
        }
        addNonNullToMap(child, "collection-frequency-seconds", metricsConfig.getCollectionFrequencySeconds());
        if (metricsConfig.getJmxConfig().isEnabled()) {
            Map<String, Object> jmxMap = new LinkedHashMap<>(1);
            jmxMap.put("enabled", metricsConfig.getJmxConfig().isEnabled());
            child.put("jmx", jmxMap);
        }
        if (metricsConfig.getManagementCenterConfig().isEnabled()) {
            Map<String, Object> mcMap = new LinkedHashMap<>(1);
            mcMap.put("enabled", metricsConfig.getManagementCenterConfig().isEnabled());
            addNonNullToMap(mcMap, "retention-seconds", metricsConfig.getManagementCenterConfig().getRetentionSeconds());
            child.put("management-center", mcMap);
        }
    }

    private static void splitBrainProtectionConfigsGenerator(Map<String, Object> parent, Config config) {
        Map<String, SplitBrainProtectionConfig> splitBrainProtectionConfigs = config.getSplitBrainProtectionConfigs();
        if (splitBrainProtectionConfigs.isEmpty()) {
            return;
        }
        Map<String, Object> child = new LinkedHashMap<>();
        parent.put("split-brain-protection", child);
        for (Map.Entry<String, SplitBrainProtectionConfig> entry : splitBrainProtectionConfigs.entrySet()) {
            Map<String, Object> configMap = new LinkedHashMap<>();
            child.put(entry.getKey(), configMap);
            addNonNullToMap(configMap, "enabled", entry.getValue().isEnabled());
            addNonNullToMap(configMap, "minimum-cluster-size", entry.getValue().getMinimumClusterSize());
            if (entry.getValue().getProtectOn() != null) {
                configMap.put("protect-on", entry.getValue().getProtectOn().name());
            }
            addNonNullToMap(configMap, "function-class-name", entry.getValue().getFunctionClassName());
            if (!entry.getValue().getListenerConfigs().isEmpty()) {
                List<String> listenersList = new ArrayList<>(entry.getValue().getListenerConfigs().size());
                for (SplitBrainProtectionListenerConfig listenerConfig : entry.getValue().getListenerConfigs()) {
                    listenersList.add(listenerConfig.getClassName());
                }
                configMap.put("listeners", listenersList);
            }
        }
    }

    public static void vectorCollectionYamlGenerator(Map<String, Object> parent, Config config) {
        var vectorsConfigAsMap = config.getVectorCollectionConfigs().values().stream().collect(Collectors.toMap(VectorCollectionConfig::getName, entry -> {
            Map<String, Object> vectorConfigAsMap = new LinkedHashMap<>();
            addNonNullToMap(vectorConfigAsMap, "backup-count", entry.getBackupCount());
            addNonNullToMap(vectorConfigAsMap, "async-backup-count", entry.getAsyncBackupCount());
            addNonNullToMap(vectorConfigAsMap, "user-code-namespace", entry.getUserCodeNamespace());
            addNonNullToMap(vectorConfigAsMap, "split-brain-protection-ref", entry.getSplitBrainProtectionName());
            addNonNullToMap(vectorConfigAsMap, "merge-policy", getMergePolicyConfigAsMap(entry.getMergePolicyConfig()));
            addNonNullToMap(vectorConfigAsMap, "indexes", vectorIndexesToList(entry.getVectorIndexConfigs()));
            return vectorConfigAsMap;
        }));
        parent.put("vector-collection", vectorsConfigAsMap);
    }

    public static void diagnosticsYamlGenerator(Map<String, Object> parent, Config config) {
        DiagnosticsConfig diagnosticsConfig = config.getDiagnosticsConfig();
        Map<String, Object> child = new LinkedHashMap<>();

        child.put("enabled", diagnosticsConfig.isEnabled());
        child.put("max-rolled-file-size-mb", diagnosticsConfig.getMaxRolledFileSizeInMB());
        child.put("max-rolled-file-count", diagnosticsConfig.getMaxRolledFileCount());
        child.put("include-epoch-time", diagnosticsConfig.isIncludeEpochTime());
        child.put("log-directory", diagnosticsConfig.getLogDirectory());
        if (diagnosticsConfig.getFileNamePrefix() != null) {
            child.put("file-name-prefix", diagnosticsConfig.getFileNamePrefix());
        }
        child.put("output-type", diagnosticsConfig.getOutputType().name());
        if (!diagnosticsConfig.getPluginProperties().isEmpty()) {
            child.put("plugin-properties", diagnosticsConfig.getPluginProperties());
        }

        parent.put("diagnostics", child);
    }

    private static List<Map<String, Object>> vectorIndexesToList(List<VectorIndexConfig> vectorIndexesConfigs) {
        return vectorIndexesConfigs.stream().map(entry -> {
            Map<String, Object> map = new HashMap<>();
            map.put("metric", entry.getMetric().name());
            map.put("dimension", entry.getDimension());
            addNonNullToMap(map, "name", entry.getName());
            addNonNullToMap(map, "max-degree", entry.getMaxDegree());
            addNonNullToMap(map, "ef-construction", entry.getEfConstruction());
            addNonNullToMap(map, "use-deduplication", entry.isUseDeduplication());
            return map;
        }).collect(Collectors.toList());
    }

    private static Map<String, Object> getWanConsumerConfigsAsMap(WanConsumerConfig wanConsumerConfig) {
        if (wanConsumerConfig == null) {
            return null;
        }

        Map<String, Object> wanConsumerConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(wanConsumerConfigAsMap, "class-name", classNameOrImplClass(wanConsumerConfig.getClassName(), wanConsumerConfig.getImplementation()));
        addNonNullToMap(wanConsumerConfigAsMap, "persist-wan-replicated-data", wanConsumerConfig.isPersistWanReplicatedData());
        addNonNullToMap(wanConsumerConfigAsMap, "properties", getPropertiesAsMap(wanConsumerConfig.getProperties()));

        return wanConsumerConfigAsMap;
    }

    private static Map<String, Object> getWanCustomPublisherConfigsAsMap(List<WanCustomPublisherConfig> wanCustomPublisherConfigs) {
        if (wanCustomPublisherConfigs == null || wanCustomPublisherConfigs.isEmpty()) {
            return null;
        }

        Map<String, Object> wanBatchPublisherConfigsAsMap = new LinkedHashMap<>();
        for (WanCustomPublisherConfig wanCustomPublisherConfig : wanCustomPublisherConfigs) {
            Map<String, Object> wanCustomPublisherConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(wanCustomPublisherConfigAsMap, "class-name", wanCustomPublisherConfig.getClassName());
            addNonNullToMap(wanCustomPublisherConfigAsMap, "properties", getPropertiesAsMap(wanCustomPublisherConfig.getProperties()));

            wanBatchPublisherConfigsAsMap.put(wanCustomPublisherConfig.getPublisherId(), wanCustomPublisherConfigAsMap);
        }

        return wanBatchPublisherConfigsAsMap;
    }

    @SuppressWarnings("checkstyle:MethodLength")
    private static Map<String, Object> getWanBatchPublisherConfigsAsMap(List<WanBatchPublisherConfig> wanBatchPublisherConfigs) {
        if (wanBatchPublisherConfigs == null || wanBatchPublisherConfigs.isEmpty()) {
            return null;
        }

        Map<String, Object> wanBatchPublisherConfigsAsMap = new LinkedHashMap<>();

        for (WanBatchPublisherConfig wanBatchPublisherConfig : wanBatchPublisherConfigs) {
            Map<String, Object> wanBatchPublisherConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(wanBatchPublisherConfigAsMap, "cluster-name", wanBatchPublisherConfig.getClusterName());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "publisher-id", wanBatchPublisherConfig.getPublisherId());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "batch-size", wanBatchPublisherConfig.getBatchSize());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "batch-max-delay-millis", wanBatchPublisherConfig.getBatchMaxDelayMillis());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "response-timeout-millis", wanBatchPublisherConfig.getResponseTimeoutMillis());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "acknowledge-type", wanBatchPublisherConfig.getAcknowledgeType().name());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "initial-publisher-state", wanBatchPublisherConfig.getInitialPublisherState().name());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "snapshot-enabled", wanBatchPublisherConfig.isSnapshotEnabled());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "idle-max-park-ns", wanBatchPublisherConfig.getIdleMaxParkNs());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "idle-min-park-ns", wanBatchPublisherConfig.getIdleMinParkNs());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "max-concurrent-invocations", wanBatchPublisherConfig.getMaxConcurrentInvocations());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "discovery-period-seconds", wanBatchPublisherConfig.getDiscoveryPeriodSeconds());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "use-endpoint-private-address", wanBatchPublisherConfig.isUseEndpointPrivateAddress());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "queue-full-behavior", wanBatchPublisherConfig.getQueueFullBehavior().name());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "max-target-endpoints", wanBatchPublisherConfig.getMaxTargetEndpoints());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "queue-capacity", wanBatchPublisherConfig.getQueueCapacity());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "properties", getPropertiesAsMap(wanBatchPublisherConfig.getProperties()));
            addNonNullToMap(wanBatchPublisherConfigAsMap, "target-endpoints", wanBatchPublisherConfig.getTargetEndpoints());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "endpoint", wanBatchPublisherConfig.getEndpoint());
            addNonNullToMap(wanBatchPublisherConfigAsMap, "sync", wrapObjectWithMap("consistency-check-strategy", wanBatchPublisherConfig.getSyncConfig().getConsistencyCheckStrategy().name()));
            addNonNullToMap(wanBatchPublisherConfigAsMap, "discovery-strategies", getDiscoveryConfigAsMap(wanBatchPublisherConfig.getDiscoveryConfig()));
            addNonNullToMap(wanBatchPublisherConfigAsMap, "discovery-strategies", getDiscoveryConfigAsMap(wanBatchPublisherConfig.getDiscoveryConfig()));

            for (AliasedDiscoveryConfig<?> aliasedDiscoveryConfig : aliasedDiscoveryConfigsFrom(wanBatchPublisherConfig)) {
                addNonNullToMap(wanBatchPublisherConfigAsMap, aliasedDiscoveryConfig.getTag(), getAliasedDiscoveryConfigAsMap(aliasedDiscoveryConfig));
            }

            wanBatchPublisherConfigsAsMap.put(wanBatchPublisherConfig.getPublisherId(), wanBatchPublisherConfigAsMap);
        }

        return wanBatchPublisherConfigsAsMap;
    }

    private static Map<String, Object> getAliasedDiscoveryConfigAsMap(AliasedDiscoveryConfig<?> aliasedDiscoveryConfig) {
        if (aliasedDiscoveryConfig == null) {
            return null;
        }

        Map<String, Object> aliasedDiscoveryConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(aliasedDiscoveryConfigAsMap, "enabled", aliasedDiscoveryConfig.isEnabled());
        addNonNullToMap(aliasedDiscoveryConfigAsMap, "use-public-ip", aliasedDiscoveryConfig.isUsePublicIp());
        for (String key : aliasedDiscoveryConfig.getProperties().keySet()) {
            addNonNullToMap(aliasedDiscoveryConfigAsMap, key, aliasedDiscoveryConfig.getProperties().get(key));
        }

        return aliasedDiscoveryConfigAsMap;
    }

    private static Map<String, Object> getDiscoveryConfigAsMap(DiscoveryConfig discoveryConfig) {
        if (discoveryConfig == null) {
            return null;
        }

        Map<String, Object> discoveryConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(discoveryConfigAsMap, "node-filter", wrapObjectWithMap("class", classNameOrImplClass(discoveryConfig.getNodeFilterClass(), discoveryConfig.getNodeFilter())));

        List<Map<String, Object>> discoveryStrategyConfigsAsList = new LinkedList<>();
        for (DiscoveryStrategyConfig discoveryStrategyConfig : discoveryConfig.getDiscoveryStrategyConfigs()) {
            Map<String, Object> discoveryStrategyConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(discoveryStrategyConfigAsMap, "enabled", "true");
            addNonNullToMap(discoveryStrategyConfigAsMap, "class", classNameOrImplClass(discoveryStrategyConfig.getClassName(), discoveryStrategyConfig.getDiscoveryStrategyFactory()));
            addNonNullToMap(discoveryStrategyConfigAsMap, "properties", getPropertiesAsMap(discoveryStrategyConfig.getProperties()));

            discoveryStrategyConfigsAsList.add(discoveryStrategyConfigAsMap);
        }

        addNonNullToMap(discoveryConfigAsMap, "discovery-strategies", discoveryStrategyConfigsAsList);

        return discoveryConfigAsMap;
    }

    private static Map<String, Object> getTieredStoreConfigAsMap(TieredStoreConfig tieredStoreConfig) {
        if (tieredStoreConfig == null) {
            return null;
        }
        Map<String, Object> tieredStoreConfigAsMap = new LinkedHashMap<>();
        addNonNullToMap(tieredStoreConfigAsMap, "enabled", tieredStoreConfig.isEnabled());
        addNonNullToMap(tieredStoreConfigAsMap, "memory-tier", getMemoryTierConfigAsMap(tieredStoreConfig.getMemoryTierConfig()));
        addNonNullToMap(tieredStoreConfigAsMap, "disk-tier", getDiskTierConfigAsMap(tieredStoreConfig.getDiskTierConfig()));

        return tieredStoreConfigAsMap;
    }

    private static Map<String, Object> getMemoryTierConfigAsMap(MemoryTierConfig memoryTierConfig) {
        if (memoryTierConfig == null) {
            return null;
        }
        Map<String, Object> memoryTierConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(memoryTierConfigAsMap, "capacity", getCapacityAsMap(memoryTierConfig.getCapacity()));

        return memoryTierConfigAsMap;
    }

    private static Map<String, Object> getCapacityAsMap(Capacity capacity) {
        if (capacity == null) {
            return null;
        }
        Map<String, Object> capacityAsMap = new LinkedHashMap<>();
        addNonNullToMap(capacityAsMap, "unit", capacity.getUnit().toString());
        addNonNullToMap(capacityAsMap, "value", String.valueOf(capacity.getValue()));

        return capacityAsMap;
    }

    private static Map<String, Object> getDiskTierConfigAsMap(DiskTierConfig diskTierConfig) {
        if (diskTierConfig == null) {
            return null;
        }
        Map<String, Object> diskTierConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(diskTierConfigAsMap, "enabled", diskTierConfig.isEnabled());
        addNonNullToMap(diskTierConfigAsMap, "device-name", diskTierConfig.getDeviceName());

        return diskTierConfigAsMap;
    }

    private static String getPartitioningStrategyAsString(PartitioningStrategyConfig partitioningStrategyConfig) {
        if (partitioningStrategyConfig == null) {
            return null;
        }

        return classNameOrImplClass(partitioningStrategyConfig.getPartitioningStrategyClass(), partitioningStrategyConfig.getPartitioningStrategy());
    }

    private static List<Map<String, Object>> getPartitioningAttributesAsList(List<PartitioningAttributeConfig> attributeConfigs) {
        if (attributeConfigs == null || attributeConfigs.isEmpty()) {
            return null;
        }

        return attributeConfigs.stream().map(DynamicConfigYamlGenerator::getPartitionAttributeAsMap).collect(Collectors.toList());
    }

    private static Map<String, Object> getPartitionAttributeAsMap(PartitioningAttributeConfig config) {
        final Map<String, Object> configAsMap = new LinkedHashMap<>();
        configAsMap.put("name", config.getAttributeName());
        return configAsMap;
    }

    private static Map<String, Object> getPredicateConfigAsMap(PredicateConfig predicateConfig) {
        if (predicateConfig == null) {
            return null;
        }

        Map<String, Object> predicateConfigAsMap = new LinkedHashMap<>();

        String type = predicateConfig.getClassName() != null ? "class-name" : "sql";

        String content = predicateConfig.getClassName() != null ? predicateConfig.getClassName() : predicateConfig.getSql();

        addNonNullToMap(predicateConfigAsMap, type, content);

        return predicateConfigAsMap;
    }

    private static Map<String, Object> getQueryCacheConfigsAsMap(List<QueryCacheConfig> queryCacheConfigs) {
        if (queryCacheConfigs == null || queryCacheConfigs.isEmpty()) {
            return null;
        }

        Map<String, Object> queryCacheConfigsAsMap = new LinkedHashMap<>();
        for (QueryCacheConfig queryCacheConfig : queryCacheConfigs) {
            Map<String, Object> queryCacheConfigAsMap = new LinkedHashMap<>();
            addNonNullToMap(queryCacheConfigAsMap, "include-value", queryCacheConfig.isIncludeValue());
            addNonNullToMap(queryCacheConfigAsMap, "in-memory-format", queryCacheConfig.getInMemoryFormat().name());
            addNonNullToMap(queryCacheConfigAsMap, "populate", queryCacheConfig.isPopulate());
            addNonNullToMap(queryCacheConfigAsMap, "coalesce", queryCacheConfig.isCoalesce());
            addNonNullToMap(queryCacheConfigAsMap, "delay-seconds", queryCacheConfig.getDelaySeconds());
            addNonNullToMap(queryCacheConfigAsMap, "batch-size", queryCacheConfig.getBatchSize());
            addNonNullToMap(queryCacheConfigAsMap, "buffer-size", queryCacheConfig.getBufferSize());
            addNonNullToMap(queryCacheConfigAsMap, "eviction", getEvictionConfigAsMap(queryCacheConfig.getEvictionConfig()));
            addNonNullToMap(queryCacheConfigAsMap, "indexes", getIndexConfigsAsList(queryCacheConfig.getIndexConfigs()));
            addNonNullToMap(queryCacheConfigAsMap, "predicate", getPredicateConfigAsMap(queryCacheConfig.getPredicateConfig()));
            addNonNullToMap(queryCacheConfigAsMap, "entry-listeners", getEntryListenerConfigsAsList(queryCacheConfig.getEntryListenerConfigs()));
            addNonNullToMap(queryCacheConfigAsMap, "serialize-keys", queryCacheConfig.isSerializeKeys());

            queryCacheConfigsAsMap.put(queryCacheConfig.getName(), queryCacheConfigAsMap);
        }

        return queryCacheConfigsAsMap;
    }

    private static Map<String, Object> getAttributeConfigsAsMap(List<AttributeConfig> attributeConfigs) {
        if (attributeConfigs == null || attributeConfigs.isEmpty()) {
            return null;
        }

        Map<String, Object> attributeConfigsAsMap = new LinkedHashMap<>();
        for (AttributeConfig attributeConfig : attributeConfigs) {
            addNonNullToMap(attributeConfigsAsMap, attributeConfig.getName(), wrapObjectWithMap("extractor-class-name", attributeConfig.getExtractorClassName()));
        }

        return attributeConfigsAsMap;
    }

    private static List<Map<String, Object>> getIndexConfigsAsList(List<IndexConfig> indexConfigs) {
        if (indexConfigs == null || indexConfigs.isEmpty()) {
            return null;
        }

        List<Map<String, Object>> indexConfigsAsList = new LinkedList<>();
        for (IndexConfig indexConfig : indexConfigs) {
            Map<String, Object> indexConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(indexConfigAsMap, "name", indexConfig.getName());
            addNonNullToMap(indexConfigAsMap, "type", indexConfig.getType().name());
            addNonNullToMap(indexConfigAsMap, "attributes", indexConfig.getAttributes());

            if (indexConfig.getType() == IndexType.BITMAP) {
                Map<String, Object> bitmapIndexOptionsAsMap = new LinkedHashMap<>();
                addNonNullToMap(bitmapIndexOptionsAsMap, "unique-key", indexConfig.getBitmapIndexOptions().getUniqueKey());
                addNonNullToMap(bitmapIndexOptionsAsMap, "unique-key-transformation", indexConfig.getBitmapIndexOptions().getUniqueKeyTransformation().name());

                indexConfigAsMap.put("bitmap-index-options", bitmapIndexOptionsAsMap);
            } else
                if (indexConfig.getType() == IndexType.SORTED) {
                    BTreeIndexConfig bTreeConf = indexConfig.getBTreeIndexConfig();
                    Map<String, Object> btreeOptionsAsMap = new LinkedHashMap<>();
                    addNonNullToMap(btreeOptionsAsMap, "page-size", getCapacityAsMap(bTreeConf.getPageSize()));

                    Map<String, Object> memoryTierAsMap = new LinkedHashMap<>();
                    addNonNullToMap(memoryTierAsMap, "capacity", getCapacityAsMap(bTreeConf.getMemoryTierConfig().getCapacity()));

                    addNonNullToMap(btreeOptionsAsMap, "memory-tier", memoryTierAsMap);
                    indexConfigAsMap.put("btree-index", btreeOptionsAsMap);
                }

            addNonNullToList(indexConfigsAsList, indexConfigAsMap);
        }

        return indexConfigsAsList;
    }

    private static Map<String, Object> getNearCacheConfigAsMap(NearCacheConfig nearCacheConfig) {
        if (nearCacheConfig == null) {
            return null;
        }

        Map<String, Object> nearCacheConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(nearCacheConfigAsMap, "name", nearCacheConfig.getName());
        addNonNullToMap(nearCacheConfigAsMap, "in-memory-format", nearCacheConfig.getInMemoryFormat().name());
        addNonNullToMap(nearCacheConfigAsMap, "invalidate-on-change", nearCacheConfig.isInvalidateOnChange());
        addNonNullToMap(nearCacheConfigAsMap, "time-to-live-seconds", nearCacheConfig.getTimeToLiveSeconds());
        addNonNullToMap(nearCacheConfigAsMap, "max-idle-seconds", nearCacheConfig.getMaxIdleSeconds());
        addNonNullToMap(nearCacheConfigAsMap, "serialize-keys", nearCacheConfig.isSerializeKeys());
        addNonNullToMap(nearCacheConfigAsMap, "cache-local-entries", nearCacheConfig.isCacheLocalEntries());
        addNonNullToMap(nearCacheConfigAsMap, "eviction", getEvictionConfigAsMap(nearCacheConfig.getEvictionConfig()));

        return nearCacheConfigAsMap;
    }

    private static Map<String, Object> getMerkleTreeConfigAsMap(MerkleTreeConfig merkleTreeConfig) {
        if (merkleTreeConfig == null || merkleTreeConfig.getEnabled() == null) {
            return null;
        }

        Map<String, Object> merkleTreeConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(merkleTreeConfigAsMap, "enabled", TRUE.equals(merkleTreeConfig.getEnabled()));
        addNonNullToMap(merkleTreeConfigAsMap, "depth", merkleTreeConfig.getDepth());

        return merkleTreeConfigAsMap;
    }

    private static Map<String, Object> getDataPersistenceConfigAsMap(DataPersistenceConfig dataPersistenceConfig) {
        if (dataPersistenceConfig == null) {
            return null;
        }

        Map<String, Object> dataPersistenceConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(dataPersistenceConfigAsMap, "enabled", dataPersistenceConfig.isEnabled());
        addNonNullToMap(dataPersistenceConfigAsMap, "fsync", dataPersistenceConfig.isFsync());
        return dataPersistenceConfigAsMap;
    }

    private static Map<String, Object> getEventJournalConfigAsMap(EventJournalConfig eventJournalConfig) {
        if (eventJournalConfig == null) {
            return null;
        }

        Map<String, Object> eventJournalConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(eventJournalConfigAsMap, "enabled", eventJournalConfig.isEnabled());
        addNonNullToMap(eventJournalConfigAsMap, "capacity", eventJournalConfig.getCapacity());
        addNonNullToMap(eventJournalConfigAsMap, "time-to-live-seconds", eventJournalConfig.getTimeToLiveSeconds());

        return eventJournalConfigAsMap;
    }

    private static Map<String, Object> getWanReplicationRefAsMap(WanReplicationRef wanReplicationRef, boolean isMap) {
        if (wanReplicationRef == null) {
            return null;
        }

        Map<String, Object> wanReplicationRefAsMap = new LinkedHashMap<>();

        addNonNullToMap(wanReplicationRefAsMap, "merge-policy-class-name", wanReplicationRef.getMergePolicyClassName());
        addNonNullToMap(wanReplicationRefAsMap, "republishing-enabled", wanReplicationRef.isRepublishingEnabled());
        addNonNullToMap(wanReplicationRefAsMap, "filters", wanReplicationRef.getFilters());

        if (isMap) {
            return wrapObjectWithMap(wanReplicationRef.getName(), wanReplicationRefAsMap);
        } else {
            addNonNullToMap(wanReplicationRefAsMap, "name", wanReplicationRef.getName());
            return wanReplicationRefAsMap;
        }
    }

    private static Map<String, Object> getEvictionConfigAsMap(EvictionConfig evictionConfig) {
        if (evictionConfig == null) {
            return null;
        }

        Map<String, Object> evictionConfigAsMap = new LinkedHashMap<>();

        String comparatorClassName = !isNullOrEmpty(evictionConfig.getComparatorClassName()) ? evictionConfig.getComparatorClassName() : null;

        addNonNullToMap(evictionConfigAsMap, "size", evictionConfig.getSize());
        addNonNullToMap(evictionConfigAsMap, "max-size-policy", evictionConfig.getMaxSizePolicy().name());
        addNonNullToMap(evictionConfigAsMap, "eviction-policy", evictionConfig.getEvictionPolicy().name());
        addNonNullToMap(evictionConfigAsMap, "comparator-class-name", comparatorClassName);

        return evictionConfigAsMap;

    }

    private static List<Map<String, Object>> getCacheSimpleEntryListenerConfigsAsList(List<CacheSimpleEntryListenerConfig> cacheSimpleEntryListenerConfigs) {
        if (cacheSimpleEntryListenerConfigs == null || cacheSimpleEntryListenerConfigs.isEmpty()) {
            return null;
        }

        List<Map<String, Object>> cacheSimpleEntryListenerConfigsAsList = new LinkedList<>();

        for (CacheSimpleEntryListenerConfig cacheSimpleEntryListenerConfig : cacheSimpleEntryListenerConfigs) {
            Map<String, Object> cacheSimpleEntryListenerConfigsAsMap = new LinkedHashMap<>();

            addNonNullToMap(cacheSimpleEntryListenerConfigsAsMap, "old-value-required", cacheSimpleEntryListenerConfig.isOldValueRequired());
            addNonNullToMap(cacheSimpleEntryListenerConfigsAsMap, "synchronous", cacheSimpleEntryListenerConfig.isSynchronous());
            addNonNullToMap(cacheSimpleEntryListenerConfigsAsMap, "cache-entry-listener-factory", wrapObjectWithMap("class-name", cacheSimpleEntryListenerConfig.getCacheEntryListenerFactory()));
            addNonNullToMap(cacheSimpleEntryListenerConfigsAsMap, "cache-entry-event-filter-factory", wrapObjectWithMap("class-name", cacheSimpleEntryListenerConfig.getCacheEntryEventFilterFactory()));

            addNonNullToList(cacheSimpleEntryListenerConfigsAsList, cacheSimpleEntryListenerConfigsAsMap);
        }

        return cacheSimpleEntryListenerConfigsAsList;
    }

    private static Map<String, Object> getExpiryPolicyFactoryConfigAsMap(CacheSimpleConfig.ExpiryPolicyFactoryConfig expiryPolicyFactoryConfig) {
        if (expiryPolicyFactoryConfig == null) {
            return null;
        }

        if (!isNullOrEmpty(expiryPolicyFactoryConfig.getClassName())) {
            return wrapObjectWithMap("class-name", expiryPolicyFactoryConfig.getClassName());
        } else {
            CacheSimpleConfig.ExpiryPolicyFactoryConfig.TimedExpiryPolicyFactoryConfig timedConfig = expiryPolicyFactoryConfig.getTimedExpiryPolicyFactoryConfig();

            if (timedConfig != null && timedConfig.getExpiryPolicyType() != null && timedConfig.getDurationConfig() != null) {
                Map<String, Object> timedConfigAsMap = new LinkedHashMap<>();
                addNonNullToMap(timedConfigAsMap, "expiry-policy-type", timedConfig.getExpiryPolicyType().name());
                addNonNullToMap(timedConfigAsMap, "duration-amount", timedConfig.getDurationConfig().getDurationAmount());
                addNonNullToMap(timedConfigAsMap, "time-unit", timedConfig.getDurationConfig().getTimeUnit().name());

                return wrapObjectWithMap("timed-expiry-policy-factory", timedConfigAsMap);
            }
            return null;
        }
    }

    private static Map<String, Object> getCollectionConfigAsMap(CollectionConfig<?> collectionConfig) {
        if (collectionConfig == null) {
            return null;
        }

        Map<String, Object> subConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(subConfigAsMap, "statistics-enabled", collectionConfig.isStatisticsEnabled());
        addNonNullToMap(subConfigAsMap, "max-size", collectionConfig.getMaxSize());
        addNonNullToMap(subConfigAsMap, "backup-count", collectionConfig.getBackupCount());
        addNonNullToMap(subConfigAsMap, "async-backup-count", collectionConfig.getAsyncBackupCount());
        addNonNullToMap(subConfigAsMap, "split-brain-protection-ref", collectionConfig.getSplitBrainProtectionName());
        addNonNullToMap(subConfigAsMap, "item-listeners", getItemListenerConfigsAsList(collectionConfig.getItemListenerConfigs()));
        addNonNullToMap(subConfigAsMap, "merge-policy", getMergePolicyConfigAsMap(collectionConfig.getMergePolicyConfig()));
        addNonNullToMap(subConfigAsMap, "user-code-namespace", collectionConfig.getUserCodeNamespace());

        return subConfigAsMap;
    }

    private static List<Map<String, Object>> getItemListenerConfigsAsList(List<? extends ListenerConfig> listenerConfigs) {
        if (listenerConfigs == null || listenerConfigs.isEmpty()) {
            return null;
        }

        List<Map<String, Object>> listenerConfigsAsList = new LinkedList<>();

        for (ListenerConfig listenerConfig : listenerConfigs) {
            Map<String, Object> listenerConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(listenerConfigAsMap, "class-name", classNameOrImplClass(listenerConfig.getClassName(), listenerConfig.getImplementation()));
            addNonNullToMap(listenerConfigAsMap, "include-value", listenerConfig.isIncludeValue());

            addNonNullToList(listenerConfigsAsList, listenerConfigAsMap);
        }

        return listenerConfigsAsList;
    }

    private static List<Map<String, Object>> getEntryListenerConfigsAsList(List<? extends ListenerConfig> listenerConfigs) {
        if (listenerConfigs == null || listenerConfigs.isEmpty()) {
            return null;
        }

        List<Map<String, Object>> listenerConfigsAsList = new LinkedList<>();
        for (ListenerConfig listenerConfig : listenerConfigs) {
            Map<String, Object> listenerConfigAsMap = new LinkedHashMap<>();

            addNonNullToMap(listenerConfigAsMap, "class-name", classNameOrImplClass(listenerConfig.getClassName(), listenerConfig.getImplementation()));
            addNonNullToMap(listenerConfigAsMap, "include-value", listenerConfig.isIncludeValue());
            addNonNullToMap(listenerConfigAsMap, "local", listenerConfig.isLocal());

            addNonNullToList(listenerConfigsAsList, listenerConfigAsMap);
        }

        return listenerConfigsAsList;
    }

    private static Map<String, Object> getPropertiesAsMap(@SuppressWarnings("rawtypes") Map<String, Comparable> properties) {
        if (properties == null || properties.isEmpty()) {
            return null;
        }

        Map<String, Object> propertiesAsMap = new LinkedHashMap<>();

        for (String key : properties.keySet()) {
            addNonNullToMap(propertiesAsMap, key, properties.get(key));
        }

        return propertiesAsMap;
    }

    private static Map<String, Object> getPropertiesAsMap(Properties properties) {
        if (properties == null || properties.isEmpty()) {
            return null;
        }

        Map<String, Object> propertiesAsMap = new LinkedHashMap<>();

        for (Object key : properties.keySet()) {
            addNonNullToMap(propertiesAsMap, key.toString(), properties.getProperty(key.toString()));
        }

        return propertiesAsMap;
    }

    private static Map<String, Object> getStoreConfigAsMap(boolean enabled, String className, String factoryClassName, Properties properties) {
        Map<String, Object> storeConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(storeConfigAsMap, "enabled", enabled);
        addNonNullToMap(storeConfigAsMap, "class-name", className);
        addNonNullToMap(storeConfigAsMap, "factory-class-name", factoryClassName);
        addNonNullToMap(storeConfigAsMap, "properties", getPropertiesAsMap(properties));

        return storeConfigAsMap;
    }

    private static Map<String, Object> getMapStoreConfigAsMap(MapStoreConfig mapStoreConfig) {
        if (mapStoreConfig == null) {
            return null;
        }

        // Note that order is different from the ConfigXmlGenerator#classNameOrImplClass().
        // Here implementation takes priority rather than classname.
        String className = mapStoreConfig.getImplementation() != null ? mapStoreConfig.getImplementation().getClass().getName() : mapStoreConfig.getClassName();
        String factoryClassName = mapStoreConfig.getFactoryImplementation() != null ? mapStoreConfig.getFactoryImplementation().getClass().getName() : mapStoreConfig.getFactoryClassName();

        Map<String, Object> mapStoreConfigAsMap = getStoreConfigAsMap(mapStoreConfig.isEnabled(), className, factoryClassName, mapStoreConfig.getProperties());

        addNonNullToMap(mapStoreConfigAsMap, "initial-mode", mapStoreConfig.getInitialLoadMode().name());
        addNonNullToMap(mapStoreConfigAsMap, "offload", mapStoreConfig.isOffload());
        addNonNullToMap(mapStoreConfigAsMap, "write-coalescing", mapStoreConfig.isWriteCoalescing());
        addNonNullToMap(mapStoreConfigAsMap, "write-delay-seconds", mapStoreConfig.getWriteDelaySeconds());
        addNonNullToMap(mapStoreConfigAsMap, "write-batch-size", mapStoreConfig.getWriteBatchSize());

        return mapStoreConfigAsMap;
    }

    private static Map<String, Object> getQueueStoreConfigAsMap(QueueStoreConfig queueStoreConfig) {
        if (queueStoreConfig == null) {
            return null;
        }

        return getStoreConfigAsMap(queueStoreConfig.isEnabled(), classNameOrImplClass(queueStoreConfig.getClassName(), queueStoreConfig.getStoreImplementation()), classNameOrImplClass(queueStoreConfig.getFactoryClassName(), queueStoreConfig.getFactoryImplementation()), queueStoreConfig.getProperties());
    }

    private static Map<String, Object> getRingbufferStoreConfigAsMap(RingbufferStoreConfig ringbufferStoreConfig) {
        if (ringbufferStoreConfig == null) {
            return null;
        }

        return getStoreConfigAsMap(ringbufferStoreConfig.isEnabled(), classNameOrImplClass(ringbufferStoreConfig.getClassName(), ringbufferStoreConfig.getStoreImplementation()), classNameOrImplClass(ringbufferStoreConfig.getFactoryClassName(), ringbufferStoreConfig.getFactoryImplementation()), ringbufferStoreConfig.getProperties());
    }

    private static List<String> getListenerConfigsAsList(List<? extends ListenerConfig> listenerConfigs) {
        if (listenerConfigs == null || listenerConfigs.isEmpty()) {
            return null;
        }

        List<String> listenerConfigsAsList = new LinkedList<>();
        for (ListenerConfig listenerConfig : listenerConfigs) {
            addNonNullToList(listenerConfigsAsList, classNameOrImplClass(listenerConfig.getClassName(), listenerConfig.getImplementation()));
        }

        return listenerConfigsAsList;
    }

    private static Map<String, Object> getMergePolicyConfigAsMap(MergePolicyConfig mergePolicyConfig) {
        if (mergePolicyConfig == null) {
            return null;
        }

        Map<String, Object> mergePolicyConfigAsMap = new LinkedHashMap<>();

        addNonNullToMap(mergePolicyConfigAsMap, "class-name", mergePolicyConfig.getPolicy());
        addNonNullToMap(mergePolicyConfigAsMap, "batch-size", mergePolicyConfig.getBatchSize());

        return mergePolicyConfigAsMap;
    }

    private static Map<String, Object> wrapObjectWithMap(String key, Object value) {
        if (value == null) {
            return null;
        }
        Map<String, Object> wrappedObject = new LinkedHashMap<>();
        wrappedObject.put(key, value);
        return wrappedObject;
    }

    private static <E> void addNonNullToList(List<E> list, E element) {
        if (element != null) {
            list.add(element);
        }
    }

    private static <K, V> void addNonNullToMap(Map<K, V> map, K key, V value) {
        if (value != null) {
            map.put(key, value);
        }
    }

    private static String getOrMaskValue(String value) {
        return maskSensitiveFields ? MASK_FOR_SENSITIVE_DATA : value;
    }

    public String generate(Config config, boolean maskSensitiveFields) {
        DynamicConfigYamlGenerator.maskSensitiveFields = maskSensitiveFields;
        Map<String, Object> document = new LinkedHashMap<>();
        Map<String, Object> root = new LinkedHashMap<>();
        document.put("hazelcast", root);

        root.put("cluster-name", config.getClusterName());

        Map<String, Object> propertiesMap = new LinkedHashMap<>();
        for (Map.Entry<Object, Object> entry : config.getProperties().entrySet()) {
            propertiesMap.put(String.valueOf(entry.getKey()), entry.getValue());
        }
        root.put("properties", propertiesMap);

        licenseKeyYamlGenerator(root, config);
        mapYamlGenerator(root, config);
        cacheYamlGenerator(root, config);
        queueYamlGenerator(root, config);
        listYamlGenerator(root, config);
        setYamlGenerator(root, config);
        multiMapYamlGenerator(root, config);
        replicatedMapYamlGenerator(root, config);
        ringbufferYamlGenerator(root, config);
        topicYamlGenerator(root, config);
        reliableTopicYamlGenerator(root, config);
        executorYamlGenerator(root, config);
        durableExecutorYamlGenerator(root, config);
        scheduledExecutorYamlGenerator(root, config);
        cardinalityEstimatorYamlGenerator(root, config);
        flakeIdGeneratorYamlGenerator(root, config);
        pnCounterYamlGenerator(root, config);
        wanReplicationYamlGenerator(root, config);
        networkConfigYamlGenerator(root, config);
        advancedNetworkConfigYamlGenerator(root, config);
        dataConnectionYamlGenerator(root, config);
        namespacesConfigGenerator(root, config);
        metricsConfigGenerator(root, config);
        splitBrainProtectionConfigsGenerator(root, config);
        vectorCollectionYamlGenerator(root, config);
        diagnosticsYamlGenerator(root, config);

        // Reset maskSensitiveFields to default
        DynamicConfigYamlGenerator.maskSensitiveFields = DEFAULT_MASK_SENSITIVE_FIELDS;

        DumpSettings dumpSettings = DumpSettings.builder().setDefaultFlowStyle(FlowStyle.BLOCK).setIndicatorIndent(INDENT - 2).setIndent(INDENT).build();
        Dump dump = new Dump(dumpSettings);
        return dump.dumpToString(document);
    }

    private void networkConfigYamlGenerator(Map<String, Object> parent, Config config) {
        if (config.getAdvancedNetworkConfig().isEnabled()) {
            return;
        }

        Map<String, Object> child = new LinkedHashMap<>();
        NetworkConfig netCfg = config.getNetworkConfig();
        addNonNullToMap(child, "public-address", netCfg.getPublicAddress());
        Map<String, Object> portCfg = new LinkedHashMap<>();
        addNonNullToMap(portCfg, "port", netCfg.getPort());
        addNonNullToMap(portCfg, "port-count", netCfg.getPortCount());
        addNonNullToMap(portCfg, "auto-increment", netCfg.isPortAutoIncrement());
        child.put("port", portCfg);
        addNonNullToMap(child, "reuse-address", netCfg.isReuseAddress());

        Collection<String> outboundPortDefinitions = netCfg.getOutboundPortDefinitions();
        if (CollectionUtil.isNotEmpty(outboundPortDefinitions)) {
            child.put("outbound-ports", new ArrayList<>(outboundPortDefinitions));
        }
        JoinConfig join = netCfg.getJoin();
        Map<String, Object> joinSubConfig = new LinkedHashMap<>();
        autoDetectionConfigYamlGenerator(joinSubConfig, join);
        multicastConfigYamlGenerator(joinSubConfig, join);
        tcpIpConfigYamlGenerator(joinSubConfig, config);
        aliasedDiscoveryConfigsYamlGenerator(joinSubConfig, aliasedDiscoveryConfigsFrom(join));
        discoveryStrategyConfigYamlGenerator(joinSubConfig, join.getDiscoveryConfig());
        addNonNullToMap(child, "join", joinSubConfig);

        interfacesConfigYamlGenerator(child, netCfg.getInterfaces());
        sslConfigYamlGenerator(child, netCfg.getSSLConfig());
        socketInterceptorConfigYamlGenerator(child, netCfg.getSocketInterceptorConfig());
        symmetricEncInterceptorConfigYamlGenerator(child, netCfg.getSymmetricEncryptionConfig());
        memberAddressProviderConfigYamlGenerator(child, netCfg.getMemberAddressProviderConfig());
        failureDetectorConfigYamlGenerator(child, netCfg.getIcmpFailureDetectorConfig());
        restApiYamlGenerator(child, netCfg.getRestApiConfig());
        memcacheProtocolYamlGenerator(child, netCfg.getMemcacheProtocolConfig());
        parent.put("network", child);
    }

    private void advancedNetworkConfigYamlGenerator(Map<String, Object> parent, Config config) {
        AdvancedNetworkConfig netCfg = config.getAdvancedNetworkConfig();
        if (!netCfg.isEnabled()) {
            return;
        }
        Map<String, Object> child = new LinkedHashMap<>();
        child.put("enabled", netCfg.isEnabled());

        JoinConfig join = netCfg.getJoin();
        Map<String, Object> joinSubConfig = new LinkedHashMap<>();
        autoDetectionConfigYamlGenerator(joinSubConfig, join);
        multicastConfigYamlGenerator(joinSubConfig, join);
        tcpIpConfigYamlGenerator(joinSubConfig, config);
        aliasedDiscoveryConfigsYamlGenerator(joinSubConfig, aliasedDiscoveryConfigsFrom(join));
        discoveryStrategyConfigYamlGenerator(joinSubConfig, join.getDiscoveryConfig());
        addNonNullToMap(child, "join", joinSubConfig);

        failureDetectorConfigYamlGenerator(child, netCfg.getIcmpFailureDetectorConfig());
        memberAddressProviderConfigYamlGenerator(child, netCfg.getMemberAddressProviderConfig());
        for (EndpointConfig endpointConfig : netCfg.getEndpointConfigs().values()) {
            endpointConfigYamlGenerator(child, endpointConfig);
        }
        parent.put("advanced-network", child);
    }
}
